/* 可选参数 */

// 使用?表示可选参数
function f(x?: number) {
  // ...
}
f(); // OK
f(10); // OK

// 提供一个参数默认值
function f1(x = 10) {
  // ...
}
f1(); // OK
f1(10); // OK

// 当一个参数可选时，它也可以是undefined
declare function f2(x?: number): void;
// cut
// All OK
f2();
f2(10);
f2(undefined);

// 回调中的可选参数============================================================================
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));

// 最新的 TypeScript 版本中并不会报错
myForEach([1, 2, 3], (a, i) => {
  console.log(i.toFixed());
  // Object is possibly 'undefined'.
});

// 不设置为可选参数就可以
function myForEach1(arr: any[], callback: (arg: any, index: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

myForEach1([1, 2, 3], (a) => {
  console.log(a);
});

myForEach1([1, 2, 3], (a, i) => {
  console.log(a);
});

/* 函数重载 */
// 可以通过写重载签名 (overlaod signatures) 说明一个函数的不同调用方法。 
// 我们需要写一些函数签名 (通常两个或者更多)，然后再写函数体的内容
function makeDate(timestamp: number): Date; // 重载签名
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) { // 实现签名
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
// Error：No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.ts(2575)
const d3 = makeDate(1, 3);

// 重载签名和实现签名=============================================================================================
function fn(x: string): void; // 重载签名
// function fn(): void; // 重载签名
function fn() { // 实现签名
  // ...
}
// Expected 1 arguments, but got 0.ts(2554)
fn();

// 实现签名必须和重载签名必须兼容（compatible）
function fn17(x: boolean): void;
// Argument type isn't right
function fn17(x: string): void;
// This overload signature is not compatible with its implementation signature.
function fn17(x: boolean) {}

function fn18(x: string): string;
// Return type isn't right
function fn18(x: number): boolean;
// This overload signature is not compatible with its implementation signature.
function fn18(x: string | number) {
  return "oops";
}

// 写一个好的函数重载的一些建议===================================================================
// 写一个函数：返回数组或者字符串的长度
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

len(""); // OK
len([0]); // OK
// 不可以传入一个可能是字符串或者数组的值，因为TS只能一次用一个函数重载处理一次函数调用
len(Math.random() > 0.5 ? "hello" : [0]);

// 替代方案：使用联合类型
// 尽可能使用联合类型来替代重载
function len2(x: string | any[]) {
  return x.length;
}
len2(""); // OK
len2([0]); // OK
// 不可以传入一个可能是字符串或者数组的值，因为TS只能一次用一个函数重载处理一次函数调用
len2(Math.random() > 0.5 ? "hello" : [0]);

// 在函数中声明this
const user47 = {
  id: 123,
  admin: false,
  becomeAdmin: function() {
    // this只想外层的user47对象
    this.admin = true;
  }
}

// TS允许在函数体内声明this的类型
type User = { id: number; admin: boolean; }
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
function getDB(): DB {
  return {
    filterUsers: function (filter: (this: User) => boolean) {
      return [
        { id: 1, admin: true },
        { id: 2, admin: false },
        { id: 3, admin: true }
      ].filter(filter);
    }
  }
}
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});
const admins47 = db.filterUsers(() => this.admin);

// 其他需要知道的类型===========================================================================
// void： The inferred return type is void
function noop() {
  return;
}

// object
// 这个特殊的类型 object 可以表示任何不是原始类型（primitive）的值 
// (string、number、bigint、boolean、symbol、null、undefined)。

// unknown
// unknown 类型表示任意值。
{
  function f1(a: any) {
    a.b(); // OK
  }
  function f2(a: unknown) {
    a.b();
    // Object is of type 'unknown'.
  }

  function safeParse(s: string): unknown {
    return JSON.parse(s);
  }
  
  // Need to be careful with 'obj'!
  const obj = safeParse('23');
}

// never
// 作为一个返回类型时，它表示这个函数会丢一个异常，或者会结束程序的执行。
{
  function fail(msg: string): never {
    throw new Error(msg);
  }
  function infiniteLoop(): never {
    while (true) {}
  }
  function fn(x: string | number) {
    if (typeof x === "string") {
      // do something
    } else if (typeof x === "number") {
      // do something else
    } else {
      x; // has type 'never'!
    }
  }
}

// Function
// 在 JavaScript，全局类型 Function 描述了 bind、call、apply 等属性，以及其他所有的函数值。
//它也有一个特殊的性质，就是 Function 类型的值总是可以被调用，结果会返回 any 类型
{
  // 这是一个无类型函数调用 (untyped function call)，这种调用最好被避免，因为它返回的是一个不安全的 any类型。
  // 如果你准备接受一个黑盒的函数，但是又不打算调用它，() => void 会更安全一些。
  function doSomething(f: Function) {
    f(1, 2, 3);
  }
}

// 剩余参数==============================================================================
{
  // 剩余参数 (rest parameters)必须放在所有参数的后面，并使用...语法
  function multiply(n: number, ...m: number[]) {
    return m.map((x) => n * x);
  }
  // 'a' gets value [10, 20, 30, 40]
  const a = multiply(10, 1, 2, 3, 4);
}

{
  // 可以借助一个使用 ... 语法的数组，为函数提供不定数量的实参
  const arr1 = [1, 2, 3];
  const arr2 = [4, 5, 6];
  arr1.push(...arr2);

  // 类型被推断为 number[] -- "an array with zero or more numbers",
  // not specifically two numbers
  const args = [8, 5];
  const angle = Math.atan2(...args); // TypeScript 并不会假定数组是不变的
  // A spread argument must either have a tuple type or be passed to a rest parameter.
  // 解决办法
  const args1 = [8, 5] as const; // 通过 as const 语法将其变为只读元组
  const angle1 = Math.atan2(...args1);
}

// 参数结构========================================================================
{
  // 参数结构 (destructuring)允许你从函数参数中提取值
  function sumArray(arr: number[]) {
    return arr.reduce((sum, curr) => sum + curr, 0);
  }
  // 函数参数结构
  function sumArray1([first, second, ...rest]: number[]) {
    return first + second + rest.length;
  }
  // 函数参数结构
  type ABC = { a: number; b: number; c: number };
  function sum({ a, b, c }: ABC) {
    console.log(a + b + c);
  }
}

// 函数的可赋值性
// 当基于上下文的类型推导（Contextual Typing）推导出返回类型为 void 的时候，并不会强制函数一定不能返回内容。
{
  type voidFunc = () => void;

  const f1: voidFunc = () => {
    return true;
  };
  
  const f2: voidFunc = () => true;
  
  const f3: voidFunc = function () {
    return true;
  };

  // 这些函数的返回值赋值给其他变量，也会维持void的类型
  const v1 = f1();
  
  const v2 = f2();
  
  const v3 = f3();

  // 当一个函数字面量定义返回一个 void 类型，函数是一定不能返回任何东西的
  function f4(): void {
    return true;
  }
  
  const f5 = function (): void {
    // @ts-expect-error 使用此指令忽略报错
    return true;
  };
}
